package pl.edu.agh.student.vrp;

import java.util.HashMap;
import java.util.Map;

import org.jgap.FitnessFunction;
import org.jgap.Gene;
import org.jgap.IChromosome;
import org.jgap.impl.IntegerGene;
import pl.edu.agh.student.vrp.utils.ChromosomeUtils;


public class MyFitnessFunction extends FitnessFunction {

    private GA vrp;
    private Map<String, Double> distances = new HashMap<String, Double>();

    public MyFitnessFunction(GA ga) {
        this.vrp = ga;
        initDistances();
    }

    private void initDistances() {
        for (Destination clientA : vrp.getDestinations()) {
            for (Destination clientB : vrp.getDestinations()) {
                if (clientA == clientB)
                    continue;
                String id = clientA.getId(clientB);
                if (!distances.containsKey(id)) {
                    Double d = new Double(clientA.distanceTo(clientB));
                    distances.put(id, d);
                }
            }
        }
    }

    @Override
    protected double evaluate(IChromosome a_subject) {

        double b_vi = 0.0D; //poczatek obslugi klienta vi

        double Q = 0.0D; //suma ilosci towarow
        double C = 0.0D; //suma kosztow przejazdu

        double B = 0.0D;
        double m = 1;
        double M = 0;
        double W = 0;
        double S = 0;
        
        int tv= vrp.getTotalVehicles();
        int nod= vrp.getDestinations().size();
        for (Gene[] route : ChromosomeUtils.getRoutes(a_subject, tv, nod)) {

            M += 1;
            //brak polaczenia baza klient
            //jednostkowy kosz przejazdu z 0 to i(do 1-szego klienta)
            double c_i = distance(0
                    , ((IntegerGene) route[0]).intValue());//route.length - 1

            //jednostkowa ilosc towaru dla 1-szego klienta na trasie
            double q_i = vrp.getDestinations().get(((IntegerGene) route[0]).intValue()).getDemand();

            C += c_i;
            Q = q_i;

            //poczatek okan czasowego dla 0
            // e dla bazy czyli 0
            double e_vi = vrp.getDestinations().get(((IntegerGene) route[0]).intValue()).getReadyTime();

            double b_v_j = vrp.getDestinations().get(0).getReadyTime();
            double delta_v_j = vrp.getDestinations().get(0).getServiceTime();
            double c_v_j = c_i;

            //System.out.println("B_vi: "+b_vi);
            //poczatek obslugi i
            //czas rozpoczacia obslugi dla i (tutaj chyba dla 1-szego klienta)
            b_vi = Math.max(e_vi, b_v_j + delta_v_j + c_v_j);

            double w_v_i = Math.max(0, b_vi - b_v_j - delta_v_j - c_v_j);
            W += w_v_i;
            
            //sprawdzenie okna dla 1-szego
            int cid = ((IntegerGene) route[0]).intValue();
            double delta_v_i = vrp.getDestinations().get(cid).getServiceTime();
            if (!checkTimeWindow(b_vi, cid) ) {
                //niezmiescilismy sie w oknie
                double dueDate = vrp.getDestinations().get(cid).getDueTime();
                    
                B += 1 * (b_vi - dueDate);
            }
            
            for (int i = 0; i < route.length - 1; ++i) {
                IntegerGene geneA = (IntegerGene) route[i]; //from
                IntegerGene geneB = (IntegerGene) route[(i + 1)]; //to
                int a = geneA.intValue();
                int b = geneB.intValue();

                int customerId = ((IntegerGene) route[i + 1]).intValue();
                int prevCustomerId = ((IntegerGene) route[i]).intValue();

                //jednostkowy kosz przejazdu z i-1 to i
                //czyli do nastapnego jak jest wiecej niz 1
                c_i = distance(a, b);
                C += c_i;

                //jego towar
                q_i = vrp.getDestinations().get(customerId).getDemand();
                Q += q_i;

                //poczatek obslugi i-1
                //czas rozpoczecia obslugi poprzedniego klienta
                b_v_j = b_vi;

                //czas obslugi i-1(poprzedniego klienta)
                delta_v_j = vrp.getDestinations().get(prevCustomerId).getServiceTime();
                c_v_j = c_i;
                //poczatek okna czasowego dla i(aktualnego klienta)
                e_vi = vrp.getDestinations().get(customerId).getReadyTime();

                //rozpoczecie obslugi aktualnego klienta
                b_vi = Math.max(e_vi, b_v_j + delta_v_j + c_v_j);

                w_v_i = Math.max(0, b_vi - b_v_j - delta_v_j - c_v_j);
                W += w_v_i;

                if (Q > vrp.getVehicleCapacity()) {
                    //nie mozemy wziasc wiecej niz ...
                    S += Q - vrp.getVehicleCapacity();
                }

                delta_v_i = vrp.getDestinations().get(customerId).getServiceTime();
                if (!checkTimeWindow(b_vi, customerId) ) {
                    //niezmiescilismy sie w oknie
                    double dueDate = vrp.getDestinations().get(customerId).getDueTime();
                    
                    B += 1 * (b_vi - dueDate);
                }
            }

            //dojazd od ostatniego klienta do bazy
            c_i = distance(((IntegerGene) route[route.length - 1]).intValue()
                    , 0);//route.length - 1
            //doliczamy do sumy koncowej
            C += c_i;

            //czas rozpoczecia obslugi poprzedniego klienta czyli ostatniego
            b_v_j = b_vi;

            //e_vi = vrp.getDestinations().get(0).getReadyTime();
            //na bazie
            //b_vi = Math.max(e_vi, b_v_j + delta_v_j + c_v_j);
            if (!(b_v_j + delta_v_j + c_v_j < vrp.getDestinations().get(0).getDueTime())) {
                //przyjedziemy po zamkniecu bazy
                double dueDate = vrp.getDestinations().get(0).getDueTime();
                b_vi = b_v_j + delta_v_j + c_v_j;
                B += (b_vi - dueDate);
            }
        }

        return 107374182300.D - (vrp.cFactor*C + 
                            vrp.mFactor*M + 
                            vrp.bFactor*B +
                            vrp.wFactor*W +
                            vrp.wFactor*S);
    }

    public double distance(int a, int b) {
        return distances.get(Destination.getId(a, b));
    }

    protected boolean checkTimeWindow(double time, int customerId) {
        //jak wogole policzyc czas przejazdu

        double readyTime = vrp.getDestinations().get(customerId).getReadyTime();
        double dueDate = vrp.getDestinations().get(customerId).getDueTime();

        if (time >= readyTime && time <= dueDate) { //e_vi <= b_vi <= l_vi
            return true;
        }

        return false;
    }

}
